<?php

class CategoryService extends FServiceBase
{
    const CACHE_KEY = 'CATEGORY';

    /**
     * Get a Category model given its ID
     *
     * @param int id Category ID
     * @return FServiceModel
     */
    public function get($params)
    {
        $model = Category::model()->findByPk($this->getParam($params, 'id', 0));
        if (!$model)
            $this->result->fail(ERROR_INVALID_DATA, Yii::t('Core.Category', 'Invalid ID.'));
        $this->result->processed('model', $model);
        return $this->result;
    }

    public function save($params)
    {
        /**
         * @var CModel
         */
        $model = $this->getModel($params['Category'], 'Category');
        $model->title = $params['Category']['title'];
        $this->result->processed('model', $model);

        if (!$model->validate())
            $this->result->fail(ERROR_INVALID_DATA, Yii::t('Core.Category', 'Submitted data is missing or invalid.'));
        elseif ($this->getParam($params, 'validateOnly', 0) == TRUE)
            return $this->result;
        elseif (!$model->save())
            $this->result->fail(ERROR_HANDLING_DB, Yii::t('Core.Category', 'Error while saving submitted data into database.'));

        //flag update cache
        $this->dataChanged();

        return $this->result;
    }


    public function delete($params)
    {
        $ids = $this->getParam($params, 'ids', array());
        if ($ids == 0) {
            return $this->result->fail(ERROR_INVALID_DATA, 'Invalid ID.');
        }

        if (!is_array($ids)) $ids = array($ids);
        $specialCatIds = trim(SiteSettings::CATEGORY_IDS_CANNOT_DELETED);
        if ($specialCatIds == '' || $specialCatIds == '0')
            $specialCatIds = array();
        else
            $specialCatIds = explode(',',$specialCatIds);
        foreach ($ids as $id) {
            $model = Category::model()->findByPk($id);
            if (is_object($model))
            {
                // check this category has childs or not
                if ($model->countChildren > 0)
                {
                    $this->result->fail(ERROR_INVALID_DATA, 'Category has some child categories.');
                    continue;
                }
                // check there is article, product or document is using this category or not
                $countArticles = Article::model()->count('category_id=:catId',array(':catId'=>$model->id));
                if ($countArticles > 0)
                {
                    $this->result->fail(ERROR_INVALID_DATA, 'Category has some articles.');
                    continue;    
                }
                $countProducts = Product::model()->count('category_id=:catId',array(':catId'=>$model->id));
                if ($countProducts > 0)
                {
                    $this->result->fail(ERROR_INVALID_DATA, 'Category has some products.');
                    continue;    
                }
                $countDocs = Document::model()->count('category_id=:catId',array(':catId'=>$model->id));
                if ($countDocs > 0)
                {
                    $this->result->fail(ERROR_INVALID_DATA, 'Category has some documents.');
                    continue;    
                }
                // check in the param CATEGORY_IDS_CANNOT_DELETED
                if (in_array($model->id, $specialCatIds))
                {
                    $this->result->fail(ERROR_INVALID_DATA, 'This category cannot be deleted.');
                    continue;    
                }
                // delete   
                Yii::import('Site.models.LanguageCategory');
                LanguageCategory::model()->deleteAll('category_id=:catId',array(':catId'=>$model->id));
                $model->delete();
            }
            else
            {
                $this->result->fail(ERROR_INVALID_DATA, 'Submitted data is missing or invalid.');
                continue;
            }
        }
        return $this->result;
    }

    public function sort($params)
    {
        $ids = $this->getParam($params, 'items', array());
        if (count($ids)) {
            foreach ($ids as $id => $parentId) {
                $orders = array();
                if ($parentId == 'root') {
                    $orders = array_keys($ids, $parentId);
                    $parentId = 0;
                }
                else {
                    $parentId = (int)$parentId;
                    $orders = array_keys($ids, $parentId);
                }
                $order = 0;
                if (count($orders))
                    $order = (int)array_search($id, $orders);
                $order++;
                Category::model()->updateByPk($id, array(
                    'parent_id' => $parentId,
                    'ordering' => $order,
                ));

                //flag update cache
                $this->dataChanged();
            }
        }
        return $this->result;
    }

    public function status($params)
    {
        $id = $this->getParam($params, 'id', 0);
        $value = $this->getParam($params, 'value', 0);
        $model = Category::model()->findByPk($id);
        if (is_object($model)) {
            $model->is_active = $value;
            if (!$model->save(false, array('is_active')))
                $this->result->fail(ERROR_HANDLING_DB, Yii::t('Core.Category', 'Error while saving submitted data into database.'));

            //flag update cache
            $this->dataChanged();
        } else
            $this->result->fail(ERROR_INVALID_DATA, Yii::t('Core.Category', 'Submitted data is missing or invalid.'));
        return $this->result;
    }

    /**
     * flag update cache
     *
     */
    protected function dataChanged()
    {
        $result = FSM::run('Admin.Cache.rebuildCacheByName', array('name' => self::CACHE_KEY));
        if ($result->hasErrors())
            return false;
        return true;
    }

    /**
     * get dependency object
     *
     * @return CCacheDependency|null
     */
    protected function getCacheDependency()
    {
        $result = FSM::run('Admin.Cache.getCacheDependency', array(
            'name' => self::CACHE_KEY,
            'description' => 'Force rebuild cache form Category',
        ));
        if (!$result->hasErrors())
            return $result->dependency;
        return null;
    }

    /**
     * get duration x second cache
     *
     * @return int
     */
    protected function getExpire()
    {
        $result = FSM::run('Admin.Cache.getExpire', array('name' => self::CACHE_KEY));
        if (!$result->hasErrors())
            return $result->duration;
        return 0;
    }

    /**
     * Build a category tree
     *
     * @param mixed $params
     */
    public function buildTree($params = null)
    {
        $id = $this->getParam($params, 'id', 0);
        $status = $this->getParam($params, 'status', null); //null => select all, 1=> only select public category

        if ($this->getExpire() && (($cache = Yii::app()->cache) !== null)) {
            $key = 'Core.services.' . __CLASS__ . '::' . __FUNCTION__ . '.' . $id;
            if (($data = $cache->get($key)) !== false) {
                $tree = unserialize($data);
                $this->result->processed('tree', $tree);
                return $this->result;
            }
        }

        //Get top level categories
        $criteria = new CDbCriteria;
        $criteria->compare('parent_id', $id);
        if (isset($status))
            $criteria->compare('is_active', $status);
        $criteria->order = 'ordering';
        $models = Category::model()->findAll($criteria);

        $tree = $this->subTree($models, $status);
        if (isset($cache)) {
            $cache->set($key, serialize($tree), $this->getExpire(), $this->getCacheDependency());
        }

        $this->result->processed('tree', $tree);
        return $this->result;
    }

    /**
     * Create subtree, internally use by Category service
     *
     * @param Category $models
     * @param int $level
     *
     * @return array
     */
    protected function subTree($models, $status = null, $level = 0)
    {
        static $data;
        if (!isset($data))
            $data = array();
        foreach ($models as $index => $model) {
            if (isset($status) && $model->is_active != $status) continue;
            $children = $model->children;
            $data[$model->id] = array(
                'title' => $model->title,
                'alias' => $model->alias,
                'level' => $level,
                'parent' => $model->parent_id,
                'children' => array(),
            );
            if (is_array($children) && count($children)) {
                $data[$model->id]['children'] = CHtml::listData($children, 'id', 'id');
                $this->subTree($children, $status, $level + 1);
            }
        }
        return $data;
    }

    /**
     * Build a sub category tree give the root ID of the sub-tree
     *
     * @param array $params
     *   - int id: Id of category which is the root of sub-tree
     */
    public function buildSubTree($params)
    {
        $id = $this->getParam($params, 'id', 0);
        $status = $this->getParam($params, 'status', null);

        if ($id == 0)
            return $this->result->processed('tree', null, 'Invalid category ID');

        return $this->buildTree(array('id' => $id, 'status' => $status));
    }

    /**
     * Get path of category Ids from root to the current category
     *
     * @param mixed $params
     *   - int id
     * @return FServiceModel
     */
    public function path($params)
    {
        $id = $this->getParam($params, 'id', 0);

        if ($id == 0)
            return $this->result->fail(ERROR_INVALID_DATA, 'Invalid category ID');

        if ($this->getExpire() && (($cache = Yii::app()->cache) !== null)) {
            $key = 'Core.services.' . __CLASS__ . '::' . __FUNCTION__ . '.' . $id;
            if (($data = $cache->get($key)) !== false) {
                $path = unserialize($data);
                $this->result->processed('path', $path);
                return $this->result;
            }
        }

        $criteria = new CDbCriteria;
        $criteria->compare('id', $id);
        $model = Category::model()->find($criteria);

        $path = array($id);
        if (is_object($model)) {
            $parent = $model->parent;
            while (is_object($parent)) {
                array_push($path, $parent->id);
                $parent = $parent->parent;
            }
        }

        if (isset($cache)) {
            $cache->set($key, serialize($path), $this->getExpire(), $this->getCacheDependency());
        }

        $this->result->processed('path', $path);
        return $this->result;
    }

    /**
     * Find categories which are childrend of a category given its Id
     * Required param:
     *   - int id: Id of parent category
     * Optional param:
     *   - bool includeStat: Whether to include statistics (total child categories and total articles)
     *   - bool skipDescription: Whether to return description data. Use this to speed up AJAX request
     *
     * @param mixed $params
     * @return FServiceModel
     */
    public function findByParentId($params)
    {
        $id = $this->getParam($params, 'id', 0);
        $withStat = $this->getParam($params, 'includeStat', 0);
        $skipDescription = $this->getParam($params, 'skipDescription', 0);

        if ($id == 0)
            return $this->result->fail(ERROR_INVALID_DATA, 'Invalid category ID');

        $criteria = new CDbCriteria;
        $criteria->compare('parent_id', $id);
        $criteria->order = 'ordering';

        if ($skipDescription) {
            $attributes = Category::model()->getAttributes();
            if (array_key_exists('description', $attributes))
                unset($attributes['description']);
            $attributes = array_keys($attributes);
            $criteria->select = $attributes;
        }

        if ($withStat)
            $criteria->with = 'countChildren';

        $models = Category::model()->findAll($criteria);

        $this->result->processed('categories', $models);
        return $this->result;
    }

    /**
     * Get Id of the current category and all of its descendant Ids
     *
     * @param mixed $params
     *   - int id
     * @return array ids: list of IDs
     */
    public function getIdAndChildren($params)
    {
        $id = $this->getParam($params, 'id', 0);
        $status = $this->getParam($params, 'status', null);

        if ($id == 0)
            return $this->result->fail(ERROR_INVALID_DATA, 'Invalid category ID');

        $ids = array($id);

        $data = $this->buildSubTree(array('id' => $id, 'status' => $status))->tree;
        if (is_array($data) && count($data))
            $ids = CMap::mergeArray($ids, array_keys($data));

        $this->result->processed('ids', $ids);
        return $this->result;
    }

    /**
     * Create an alias
     *
     * @param mixed $params
     * @return String alias
     */
    public function createAlias($title)
    {
        return Utility::createAlias(new Category(), $title);
    }
    
    public function getRootAndChildrenIds($params)
    {
        $rootId = $this->getParam($params, 'id', 0);
        $status = $this->getParam($params, 'status', null);       
        $con = Yii::app()->db;
        $catIds = array();
        $this->getChildrenIds($rootId, $status, $con, $catIds);      
        $this->result->processed('ids', $catIds);
        return $this->result;
    }
    
    protected function getChildrenIds($id, $status, $con, &$catIds)
    {
        $catIds[] = $id;
        $sql = 'SELECT id FROM category WHERE parent_id = '.$id.' AND is_active = '.$status;                        
        $cmd = $con->createCommand($sql);
        $rows = $cmd->queryAll();
        if (is_array($rows) && count($rows) > 0)
        {
            foreach($rows as $row)
            {
                $this->getChildrenIds($row['id'], $status, $con, $catIds);
            }
        }
    }
    
    public function getParentIds($params)
    {
        $catId = $this->getParam($params, 'id', 0);
        $includeChildCat = $this->getParam($params, 'include_child_cat', 1);
        $parentCatIds = array();
        if ($catId <= 0)
        {
            $this->result->processed('ids', array());
            return $this->result;    
        }
        $cat = Category::model()->findByPk($catId);
        if (is_null($cat))
        {
            $this->result->processed('ids', array());
            return $this->result;    
        }
        if ($includeChildCat == 1)
            $parentCatIds[] = $cat->id;
        if ($cat->parent_id == 0)
        {
            $this->result->processed('ids', $parentCatIds);
            return $this->result;   
        }
        $parentId = $cat->parent_id;
        $isTopParent = false;
        while(!$isTopParent)
        {
            $parent_cat = Category::model()->findByPk($parentId);
            if (is_null($parent_cat)) break;
            $parentCatIds[] = $parent_cat->id;
            if ($parent_cat->parent_id == 0)
            {
                $isTopParent = true;    
            }
            else
            {
                $parentId = $parent_cat->parent_id;
            }
        }         
        $this->result->processed('ids', $parentCatIds);
        return $this->result;
    } 
}